5. Design System - Naive UI
Utiliser une librairie de composants UI pour créer des interfaces modernes et cohérentes
Dans ce chapitre, on reprend le Show Watchlist du chapitre précédent et on remplace chaque élément HTML brut par son équivalent Naive UI.
Pourquoi utiliser une librairie UI ?
Sans librairie UI, chaque bouton, formulaire ou mise en page doit être conçu et stylisé manuellement. Une librairie comme Naive UI offre :
- Des composants préconçus (boutons, inputs, cartes, grilles…)
- Un design cohérent appliqué uniformément
- Des composants accessibles et testés
- Un gain de temps pour se concentrer sur la logique métier
Installation et configuration
npm install naive-ui
Enregistrement global dans main.ts — tous les composants sont disponibles partout sans import supplémentaire :
// main.ts
import naive from 'naive-ui'
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.use(naive)
app.mount('#app')
Remarque : Dans Naive UI, le
v-modeldes inputs s'écritv-model:valueau lieu dev-model. C'est une particularité de la librairie.
Composants essentiels
NButton
Remplace les <button> HTML. Plusieurs types visuels disponibles.
<template>
<NButton type="primary" @click="emit('toggle-seen', show)">Basculer</NButton>
</template>
Types disponibles : default, primary, info, success, warning, error.
NInput
Remplace les <input> HTML. Utilise v-model:value au lieu de v-model.
<template>
<NInput v-model:value="search" placeholder="Rechercher une série..."/>
</template>
NTag
Remplace les <span> de statut. Idéal pour afficher l'état vu/à voir d'une série.
<template>
<NTag :type="show.seen ? 'success' : 'default'">
{{ show.seen ? '✓ Vu' : 'À voir' }}
</NTag>
</template>
NCard
Remplace les <li> ou <div> de carte. Propose des slots #header-extra et #footer.
<template>
<NCard :title="show.title">
<template #header-extra>
<NTag :type="show.seen ? 'success' : 'default'">
{{ show.seen ? '✓ Vu' : 'À voir' }}
</NTag>
</template>
<p>{{ show.genre }} — {{ show.year }}</p>
<template #footer>
<NButton type="primary" @click="emit('toggle-seen', show)">Basculer</NButton>
</template>
</NCard>
</template>
NGrid et NGi
Remplace la <ul> / <li> pour une mise en page en grille responsive.
<template>
<NGrid :cols="3" :x-gap="16" :y-gap="16">
<NGi v-for="show in shows" :key="show.id">
<ShowCard :show="show" @toggle-seen="emit('toggle-seen', $event)"/>
</NGi>
</NGrid>
</template>
NSpace
Gère l'espacement et l'alignement entre éléments, en remplacement des <div> de layout.
<template>
<NSpace justify="space-between" align="center">
<NInput v-model:value="search" placeholder="Rechercher une série..."/>
<NTag type="success">{{ seenShows.length }} vu(s) sur {{ shows.length }}</NTag>
</NSpace>
</template>
Exemple complet
Le Show Watchlist du chapitre précédent, avec tous les éléments HTML remplacés par Naive UI.
src/components/ShowCard.component.vue
<template>
<NCard :title="show.title">
<template #header-extra>
<NTag :type="show.seen ? 'success' : 'default'">
{{ show.seen ? '✓ Vu' : 'À voir' }}
</NTag>
</template>
<p>{{ show.genre }} — {{ show.year }}</p>
<template #footer>
<NButton type="primary" @click="emit('toggle-seen', show)">Basculer</NButton>
</template>
</NCard>
</template>
<script setup lang="ts">
import type { Show } from '@/types'
interface ShowCardProps {
show: Show
}
defineProps<ShowCardProps>()
const emit = defineEmits<{
'toggle-seen': [show: Show]
}>()
</script>
src/components/ShowList.component.vue
<template>
<NSpace vertical :size="16">
<slot></slot>
<p v-if="shows.length === 0">Aucune série pour l'instant.</p>
<NGrid :cols="3" :x-gap="16" :y-gap="16">
<NGi v-for="show in shows" :key="show.id">
<ShowCard :show="show" @toggle-seen="emit('toggle-seen', $event)"/>
</NGi>
</NGrid>
</NSpace>
</template>
<script setup lang="ts">
import type { Show } from '@/types'
import ShowCard from './ShowCard.component.vue'
interface ShowListProps {
shows: Show[]
}
defineProps<ShowListProps>()
const emit = defineEmits<{
'toggle-seen': [show: Show]
}>()
</script>
src/pages/HomePage.vue
<template>
<div>
<ShowList :shows="shows" @toggle-seen="onToggleSeen">
<NSpace justify="space-between" align="center">
<NInput v-model:value="search" placeholder="Rechercher une série..."/>
<NTag type="success">{{ seenShows.length }} vu(s) sur {{ shows.length }}</NTag>
</NSpace>
</ShowList>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue'
import type { Show } from '@/types'
import ShowList from '../components/ShowList.component.vue'
const search = ref<string>('')
const shows = ref<Show[]>([
{ id: 1, title: 'Inception', genre: 'Sci-Fi', year: 2010, seen: true },
{ id: 2, title: 'The Dark Knight', genre: 'Action', year: 2008, seen: false },
{ id: 3, title: 'Interstellar', genre: 'Sci-Fi', year: 2014, seen: false },
{ id: 4, title: 'Parasite', genre: 'Thriller', year: 2019, seen: true },
{ id: 5, title: 'Dune', genre: 'Sci-Fi', year: 2021, seen: false },
{ id: 6, title: 'Everything Everywhere All at Once', genre: 'Action', year: 2022, seen: true },
])
const seenShows = computed(() =>
shows.value.filter((m) => m.seen)
)
watch(search, (newVal) => {
console.log('Recherche :', newVal)
})
onMounted(() => {
console.log('Application prête')
})
const onToggleSeen = (show: Show) => {
show.seen = !show.seen
}
</script>